package com.zxx.flowerstore.utils;

import com.zxx.flowerstore.utils.utils.ListUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @ClassName RedisRepository
 * @Author Admin
 * @Date 2021/3/17 20:57
 * @Description RedisRepository
 * @Version 1.0
 */
@Component
public class RedisRepository<K, V> {
    private static final int BATCH_LENGTH = 10000;

    @Autowired
    private RedisTemplate<K, V> redisTemplate;

    public RedisTemplate RedisRepository() {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        redisTemplate.setStringSerializer(stringSerializer);
        // explicitly enable transaction support
        redisTemplate.setEnableTransactionSupport(true);
        return redisTemplate;
    }

    public BoundValueOperations<K, V> getBoundValueOps(K key) {

        return redisTemplate.boundValueOps(key);
    }

    public BoundZSetOperations<K, V> getBoundZSetOps(K key) {
        return redisTemplate.boundZSetOps(key);
    }

    public BoundSetOperations<K, V> getBoundSetOps(K key) {
        return redisTemplate.boundSetOps(key);
    }

    public BoundListOperations<K, V> getBoundListOps(K key) {
        return redisTemplate.boundListOps(key);
    }

    public <HK, HV> BoundHashOperations<K, HK, HV> getBoundHashOps(K key) {
        return redisTemplate.boundHashOps(key);
    }

    // Key
    public void del(final K key) {
        redisTemplate.delete(key);
    }

    public void del(final Collection<K> keys) {
        redisTemplate.delete(keys);
    }

    public Boolean exists(final K key) {
        return redisTemplate.hasKey(key);
    }

    public Boolean expire(final K key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    public void expireAt(final K key, Date date) {
        redisTemplate.expireAt(key, date);
    }

    public Set<K> keys(final K pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 获取keys
     *
     * @param pattern
     * @return java.util.Set<java.lang.String>
     * @author admin
     * @date 2021/1/21 13:18
     * @version: 1.0
     */
    public Set<String> scan(String pattern) {
        return this.scan(pattern, BATCH_LENGTH);
    }

    /**
     * 获取keys
     *
     * @param pattern
     * @param count
     * @return java.util.Set<java.lang.String>
     * @author admin
     * @date 2021/1/21 13:27
     * @version: 1.0
     */
    public Set<String> scan(String pattern, int count) {
        Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keysTmp = new HashSet<>();
            try (Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
                    .match(pattern)
                    .count(count)
                    .build())) {
                while (cursor.hasNext()) {
                    keysTmp.add(new String(cursor.next()));
                }
                return keysTmp;
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        });
        return keys;
    }

    public String type(final K key) {
        return redisTemplate.type(key).code();
    }

    public V get(final K key) {
        BoundValueOperations<K, V> ops = this.getBoundValueOps(key);
        return ops.get();
    }

    public V getSet(final K key, final V value) {
        BoundValueOperations<K, V> ops = this.getBoundValueOps(key);
        return ops.getAndSet(value);
    }

    public Long incr(final K key, final long delta) {
        BoundValueOperations<K, V> ops = this.getBoundValueOps(key);
        return ops.increment(delta);
    }

    public void set(final K key, final V value) {
        BoundValueOperations<K, V> ops = this.getBoundValueOps(key);
        ops.set(value);
    }

    public void set(final K key, final V value, final long timeout, final TimeUnit unit) {
        BoundValueOperations<K, V> ops = this.getBoundValueOps(key);
        ops.set(value, timeout, unit);
    }

    // Hash
    public void hDel(final K key, final Object... hKeys) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        ops.delete(hKeys);
    }

    public Boolean hExists(final K key, final K hKeys) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        return ops.hasKey(hKeys);
    }

    public Map<K, V> hGet(final K key) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        return ops.entries();
    }

    /**
     * 批量获取值
     *
     * @param keys
     * @return java.util.List<java.lang.Object>
     * @author zhengxin
     * @date 2021/1/22 14:46
     * @version: 1.0
     */
    public List<Object> batchHGet(Set<String> keys) {
        return this.batchHGet(keys, BATCH_LENGTH);
    }

    /**
     * 批量获取值
     *
     * @param keys
     * @param splitLength
     * @return java.util.List<java.lang.Object>
     * @author zhengxin
     * @date 2021/1/22 14:46
     * @version: 1.0
     */
    public List<Object> batchHGet(Set<String> keys, int splitLength) {
        List<Object> values = ListUtils.split(new ArrayList<>(keys), splitLength)
                .stream()
                .map(list -> redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                    for (String key : list) {
                        connection.hGetAll(key.getBytes());
                    }
                    return null;
                })).flatMap(Collection::stream)
                .collect(Collectors.toList());

        return values;
    }

    public V hGet(final K key, final K hKey) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        return ops.get(hKey);
    }

    public Set<K> hKeys(final K key) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        return ops.keys();
    }

    public Long hLen(final K key) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        return ops.size();
    }

    public void hSet(final K key, final K hk, final V hv) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        ops.put(hk, hv);
    }

    /**
     * 批量设置hash的指定key对应的value
     *
     * @param keys
     * @param hKey
     * @param hValue
     * @return void
     * @author zhengxin
     * @date 2021/1/25 10:59
     * @version: 1.0
     */
    public void batchHSet(Set<String> keys, String hKey, String hValue) {
        ListUtils.split(new ArrayList<>(keys), BATCH_LENGTH)
                .forEach(list -> redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                    for (String key : list) {
                        connection.hSet(key.getBytes(), hKey.getBytes(), hValue.getBytes());
                    }
                    return null;
                }));
    }

    public void hSet(final K key, final Map<K, V> map) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        ops.putAll(map);
    }

    public List<V> hVals(final K key) {
        BoundHashOperations<K, K, V> ops = this.getBoundHashOps(key);
        return ops.values();
    }

    // List

    public V lIndex(final K key, final long index) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.index(index);
    }

    public void lInsert(final K key, final long index, V value) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        ops.set(index, value);
    }

    public Long lLen(final K key) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.size();
    }

    public V lPop(final K key) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.leftPop();
    }

    public V lPop(final K key, long timeout, TimeUnit unit) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.leftPop(timeout, unit);
    }

    public Long lPush(final K key, final V value) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.leftPush(value);
    }

    public List<V> lRange(final K key, final long start, final long end) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.range(start, end);
    }

    public Long lRem(final K key, final long index, final V value) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.remove(index, value);
    }

    public void lSet(final K key, final long index, final V value) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        ops.set(index, value);
    }

    public void ltrim(final K key, final long start, final long end) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        ops.trim(start, end);
    }

    public Long rPush(final K key, final V value) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.rightPush(value);
    }

    public V rPop(final K key) {
        BoundListOperations<K, V> ops = this.getBoundListOps(key);
        return ops.rightPop();
    }

    // Set

    public Long sAdd(final K key, final V value) {
        BoundSetOperations<K, V> ops = this.getBoundSetOps(key);
        return ops.add(value);
    }

    public Set<V> sDiff(final K key) {
        BoundSetOperations<K, V> ops = this.getBoundSetOps(key);
        return ops.diff(key);
    }

    public Set<V> sMembers(final K key) {
        BoundSetOperations<K, V> ops = this.getBoundSetOps(key);
        return ops.members();
    }

    public Boolean sIsMember(final K key, final V value) {
        BoundSetOperations<K, V> ops = this.getBoundSetOps(key);
        return ops.isMember(value);
    }

    public V sPop(final K key) {
        BoundSetOperations<K, V> ops = this.getBoundSetOps(key);
        return ops.pop();
    }

    public Long sRem(final K key, final V value) {
        BoundSetOperations<K, V> ops = this.getBoundSetOps(key);
        return ops.remove(value);
    }

    public Long sCard(K key) {
        BoundSetOperations<K, V> ops = this.getBoundSetOps(key);
        return ops.size();
    }

    // SortedSet

    public void zAdd(final K key, final V value, final double score) {
        BoundZSetOperations<K, V> ops = this.getBoundZSetOps(key);
        ops.add(value, score);
    }

    public Set<V> zRange(final K key, final long start, final long end) {
        BoundZSetOperations<K, V> ops = this.getBoundZSetOps(key);
        return ops.range(start, end);
    }

    public Long zRem(final K key, final Object... values) {
        BoundZSetOperations<K, V> ops = this.getBoundZSetOps(key);
        return ops.remove(values);
    }

    public Long zCard(K key) {
        BoundZSetOperations<K, V> ops = this.getBoundZSetOps(key);
        return ops.zCard();
    }

    public RedisTemplate<K, V> getRedisTemplate() {
        return redisTemplate;
    }


    public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}
